[JavaScript] Promise實作原理

前言

最近從 JavaScript Promises … In Wicked Detail 研究Promise的實作原理,在此紀錄一下。

這邊的Promise會盡量符合Promises/A+的規範但離完整的實作仍會有些細節上處理的差距。

基礎

假設我們希望將callback改成then的串鏈形式,我們只需要簡單改變結構。

1
2
3
4
5
6
7
8
doSomething(function(value) {
console.log('Got a value:' + value);
});
function doSomething(callback) {
var value = 42;
callback(value);
}

改變成

1
2
3
4
5
6
7
8
9
10
11
12
doSomething().then(function(value) {
console.log('Got a value:' + value);
});
function doSomething() {
return {
then: function(callback) {
var value = 42;
callback(value);
}
};
}

Promise

接下來開始實作Promise物件,使他適用到任何函式中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
};
function resolve(value) {
callback(value);
}
fn(resolve);
}
var promise = new Promise(function(resolve) {
var value = 42;
resolve(value);
}) ;
promise.then(function(value) {
console.log('Got a value:' + value);
});

這樣子的結構會有個問題,因為resolve會在then之前執行,所以callback目前的狀態還是null。

在這邊利用setTimeout讓resolve跳出目前的事件流,讓then在resolve前執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
};
function resolve(value) {
setTimeout(function(){
callback(value);
},0)
}
fn(resolve);
}
var promise = new Promise(function(resolve) {
var value = 42;
resolve(value);
}) ;
promise.then(function(value) {
console.log('Got a value:' + value);
});

乍看之下好像完成了,但是一旦then是非同步執行,這樣的結構就會出問題。

因為如此一來resolve一樣在then之前執行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
};
function resolve(value) {
setTimeout(function(){
callback(value);
},0)
}
fn(resolve);
}
var promise = new Promise(function(resolve) {
var value = 42;
resolve(value);
}) ;
setTimeout(function(){
promise.then(function(value) {
console.log('Got a value:' + value);
});
},100)

狀態機

可以將狀態結果記錄在Promise中,利用當前狀態判斷執行順序再將結果傳回欲執行的函式。

若then在resolve前執行則先暫時將then的函式存在deferred中,等候resolve完才執行。

反之若resolve在then前執行,則因為狀態已改變為resolved,可直接接著執行then函式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function Promise(fn) {
var state = 'pending';
var value;
var deferred;
function resolve(newValue) {
value = newValue;
state = 'resolved';
if(deferred) {
handle(deferred);
}
}
function handle(onResolved) {
if(state === 'pending') {
deferred = onResolved;
return;
}
onResolved(value);
}
this.then = function(onResolved) {
handle(onResolved);
};
fn(resolve);
}
var promise = new Promise(function(resolve) {
setTimeout(function(){
var value = 42;
resolve(value);
},500);
}) ;
promise.then(function(value) {
console.log('1. execute then before resolve , value :' + value);
});
promise.then(function(value) {
console.log('2. execute then before resolve , value :' + value);
});
setTimeout(function(){
promise.then(function(value) {
console.log('3. execute then before resolve , value :' + value);
});
},300);
setTimeout(function(){
promise.then(function(value) {
console.log('execute then after resolve , value :' + value);
});
},1000);

可以發現如果有多個then在resolve前執行會只有最後一個then會執行,這是因為不斷將deferred覆蓋過去了,所以實作上會利用陣列來儲存函式。

串鏈Promises

若我們希望then之後可以接著then,如此一來我們必須再then中傳回Promise物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
value = newValue;
state = 'resolved';
if(deferred) {
handle(deferred);
}
}
function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
if(!handler.onResolved) {
handler.resolve(value);
return;
}
var ret = handler.onResolved(value);
handler.resolve(ret);
}
this.then = function(onResolved) {
return new Promise(function(resolve) {
handle({
onResolved: onResolved,
resolve: resolve
});
});
};
fn(resolve);
}
var promise = new Promise(function(resolve) {
var value = 42;
resolve(value);
}).then(function(result) {
console.log("First result:" + result);
return result+1;
}).then(function(result) {
console.log("Second result:" + result);
return result+1;
}).then(function(result) {
console.log("Third result:" + result);
});

一般建立的Promise和then建立的Promise有些許的不同

在then建立的Promise中我們把原先的value傳進去執行then中的函式,再將執行結果傳給原先Promise的resolve。

1
2
var ret = handler.onResolved(value);
handler.resolve(ret);

文中給的then不一定要有callback函式,因此在handle函式做了判斷

1
2
3
4
if(!handler.onResolved) {
handler.resolve(value);
return;
}

不過我暫時想不到什麼情況下需要一個沒有callback的then。

串鏈中回傳Promise

then中的程式如果是非同步執行,如此一來必須在串鏈中回傳Promise。

1
2
3
4
5
6
7
8
9
10
11
12
promise.then(function(result) {
return new Promise(function(resolve){
setTimeout(function(){
console.log("First result:" + result);
resolve(result+1);
},500)
});
}).then(function(anotherPromise) {
anotherPromise.then(function(finalResult) {
console.log("the final result is", finalResult);
});
});

我們可以將判別是否為Promise的程式寫到resolve中,我們會不斷的遞迴呼叫resolve函式直到不再是Promise為止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
if(newValue && typeof newValue.then === 'function') {
newValue.then(resolve);
return;
}
state = 'resolved';
value = newValue;
if(deferred) {
handle(deferred);
}
}
function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
if(!handler.onResolved) {
handler.resolve(value);
return;
}
var ret = handler.onResolved(value);
handler.resolve(ret);
}
this.then = function(onResolved) {
return new Promise(function(resolve) {
handle({
onResolved: onResolved,
resolve: resolve
});
});
};
fn(resolve);
}
var promise = new Promise(function(resolve) {
var value = 42;
resolve(value);
})
promise.then(function(result) {
return new Promise(function(resolve){
setTimeout(function(){
console.log("First result:" + result);
resolve(result+1);
},500)
});
}).then(function(result) {
console.log("Second result:" + result);
return result+1;
}).then(function(result) {
console.log("Third result:" + result);
});

其他

錯誤處理及一些實作上的細節等到之後補充。

參考

JavaScript Promises … In Wicked Detail